﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TekaMUD.Engine.Blocks;
using TekaMUD.Engine.IO;
using TekaMUD.Engine.Framework;
using TekaMUD.Engine.Commands.Parser;

namespace TekaMUD.Engine.Commands
{
	public delegate void CommandHandler(Command cmd);
	public delegate void ComplexActionHandler(Command cc);

	public class Command
	{
		#region Attributes and Properties
		public readonly Object EmptyCommandMarker = new Object();
		public BaseChannel Channel;
		public Player Player;
		public bool EmptyCommand { get; private set; }
		public CommandNode CommandNode { get; set; }

		/// <summary>
		/// Returns an array of all the bounded objects for the command's arguments.
		/// </summary>
		public GameEntity[] BoundArguments { get; private set; }

		/// <summary>
		/// Returns the bounded target object for the command.
		/// </summary>
		public GameEntity BoundTarget { get; private set; }
		
		/* Command table */
		private static Dictionary<string, CommandHandler> CommandTable = new Dictionary<string, CommandHandler>();
		private static Dictionary<string, ComplexActionHandler> ActionTable = new Dictionary<string, ComplexActionHandler>();

		#endregion

		#region Constructors

		static Command()
		{
			CommandTable = new Dictionary<string, CommandHandler>();
			

			CommandTable.Add("ping", new CommandHandler(BasicCommands.Ping));
			CommandTable.Add("sys", new CommandHandler(BasicCommands.SystemStats));
			CommandTable.Add("terminate", new CommandHandler(BasicCommands.Terminate));
			CommandTable.Add("listcontinents", new CommandHandler(LandCommands.ListContinents));
			CommandTable.Add("commit", new CommandHandler(BasicCommands.Commit));
			CommandTable.Add("close", new CommandHandler(BasicCommands.Close));
			CommandTable.Add("inventory", new CommandHandler(InventoryActions.Show_Inventory));
			CommandTable.Add("inv", new CommandHandler(InventoryActions.Show_Inventory));
			CommandTable.Add("weather", new CommandHandler(ExplorationActions.Weather));
			CommandTable.Add("time", new CommandHandler(ExplorationActions.Time));
			CommandTable.Add("n", new CommandHandler(MovementCommands.Go_North));
			CommandTable.Add("s", new CommandHandler(MovementCommands.Go_South));
			CommandTable.Add("w", new CommandHandler(MovementCommands.Go_West));
			CommandTable.Add("e", new CommandHandler(MovementCommands.Go_East));


			ActionTable.Add("look", new ComplexActionHandler(ExplorationActions.Look));
			ActionTable.Add("pick", new ComplexActionHandler(InventoryActions.Pick));
			ActionTable.Add("drop", new ComplexActionHandler(InventoryActions.Drop));
			ActionTable.Add("get", new ComplexActionHandler(InventoryActions.Pick));
			ActionTable.Add("open", new ComplexActionHandler(ExplorationActions.Open));
			ActionTable.Add("set", new ComplexActionHandler(ManagementCommands.Set));
			ActionTable.Add("examine", new ComplexActionHandler(ExplorationActions.Examine));
		}

		

		public Command(CommandNode cnode)
		{
			CommandNode = cnode;
		}

		public Command()
		{
		}

		#endregion

		public virtual void Execute()
		{
			CommandHandler handler = null;

			switch (Player.Status)
			{
				case PlayerStatus.New:
					// First handler.
					PlayerMgmtCommands.HandleName(this);

					break;

				case PlayerStatus.Registering:
					handler = Channel.PopNextHandler();

					if (handler != null)
						handler(this);
					else
						HandlerInvalidCommand();

					return;

				default:

					if (CommandNode is EmptyCommandNode)
					{
						Channel.SendPrompt();
					}
					else
					{

						handler = Channel.PopNextHandler();

						if (handler != null)
							handler(this);

						else if (Command.CommandTable.ContainsKey(CommandNode.Name))
							Command.CommandTable[CommandNode.Name](this);

						else if (Command.ActionTable.ContainsKey(CommandNode.Name))
							ExecuteAction();

						else
							HandlerInvalidCommand();
						return;
					}
					break;
			}
		}

		protected void HandlerInvalidCommand()
		{
			switch(Player.Status)
			{
				case PlayerStatus.New:
				case PlayerStatus.Registering:
					return;
				default:
					Messenger.SendAgent(Player, "\nInvalid command: " + CommandNode.FullText + ".\n");
					break;
			}	
		}

		/// <summary>
		/// Entry point for complex commands execution.
		/// </summary>
		public void ExecuteAction()
		{
			ComplexActionHandler target = ActionTable[CommandNode.Name];
			
			// Deny actions to un-active players.
			if (Player.Status != PlayerStatus.Active || target == null)
			{
				Messenger.SendAgent(Player, "\r\nInvalid command: " + CommandNode.FullText + "\n");

				return;
			}

			// Hack: Avoid parsing arguments when the command doesn't need them.
			if (CommandNode.Name != "set")
			{
				BoundArguments = BuildObjectList(CommandNode.ArgumentList);
			}

			// Invoke the command.
			target(this);

			return;
		}

		/// <summary>
		/// Iterates over a list of command Nouns and tries to match each and everyone to a GameObject or Player
		/// </summary>
		private GameEntity[] BuildObjectList(ArgumentList nouns)
		{
			List<GameEntity> objectList = new List<GameEntity>();

			if (nouns == null)
				return objectList.ToArray();

			foreach (ArgumentNode noun in nouns)
			{
				// Look for the room itself. This is final.
				if (noun.Name.Equals("room", StringComparison.InvariantCultureIgnoreCase))
				{
					objectList.Add(Player.CurrentRoom);
					break;
				}

				// Look for players
				foreach (Player p in World.Current.GetPlayersByRoom(Player.CurrentRoom))
					if (p.Name.Equals(noun.Name, StringComparison.InvariantCultureIgnoreCase) || noun.Name.Equals("players", StringComparison.InvariantCultureIgnoreCase))
						objectList.Add(p);

				// Look for NPC's
				if (noun.NounComponents.Length > 1)
				{
					foreach (NPC npc in World.Current.GetNpcByRoom(Player.CurrentRoom))
						if (npc.Name.Equals(noun.Name, StringComparison.InvariantCultureIgnoreCase) || npc.MatchName(noun.NounComponents[1], noun.NounComponents[0]))
							objectList.Add(npc);
				}
				else
				{
					foreach (NPC npc in World.Current.GetNpcByRoom(Player.CurrentRoom))
						if (npc.Name.Equals(noun.Name, StringComparison.InvariantCultureIgnoreCase) || npc.MatchName(noun.NounComponents[0], null))
							objectList.Add(npc);
				}


				// Look for objects in the room.
				if (noun.NounComponents.Length > 1)
				{
					foreach (GameObject go in Player.CurrentRoom.RoomObjects.Contents)
						if (go.MatchName(noun.NounComponents[1], noun.NounComponents[0]))
							objectList.Add(go);
				}
				else
				{
					foreach (GameObject go2 in Player.CurrentRoom.RoomObjects.Contents)
						if (go2.MatchName(noun.NounComponents[0], null))
							objectList.Add(go2);
				}

				// Look in the player's backpack.
				if (noun.NounComponents.Length > 1)
				{
					foreach (GameObject go in Player.Backpack.Contents)
						if (go.MatchName(noun.NounComponents[1], noun.NounComponents[0]))
							objectList.Add(go);
				}
				else
				{
					foreach (GameObject go2 in Player.Backpack.Contents)
						if (go2.MatchName(noun.NounComponents[0], null))
							objectList.Add(go2);
				}

				// Look for geometry (passages, doors, windows).

				// doors.
				foreach (KeyValuePair<EnumDirectionType, RoomConnection> kvp in Player.CurrentRoom.GetAllConnections())
					if (kvp.Value.Door != null)
					{
						RoomConnection connection = kvp.Value;

						if (connection.Door.MatchName(noun))
							objectList.Add(connection.Door);
					}
			}

			return objectList.ToArray();
		}
	}
}